package com.hqd.ch03.v50.boot;

import com.hqd.ch03.v50.context.ApplicationListener;

import java.util.function.Supplier;

public interface BootstrapRegistry {

    /**
     * Register a specific type with the registry. If the specified type has already been
     * registered and has not been obtained as a {@link Scope#SINGLETON singleton}, it
     * will be replaced.
     *
     * @param <T>              the instance type
     * @param type             the instance type
     * @param instanceSupplier the instance supplier
     */
    <T> void register(Class<T> type, InstanceSupplier<T> instanceSupplier);

    /**
     * Register a specific type with the registry if one is not already present.
     *
     * @param <T>              the instance type
     * @param type             the instance type
     * @param instanceSupplier the instance supplier
     */
    <T> void registerIfAbsent(Class<T> type, InstanceSupplier<T> instanceSupplier);

    /**
     * Return if a registration exists for the given type.
     *
     * @param <T>  the instance type
     * @param type the instance type
     * @return {@code true} if the type has already been registered
     */
    <T> boolean isRegistered(Class<T> type);

    /**
     * Return any existing {@link InstanceSupplier} for the given type.
     *
     * @param <T>  the instance type
     * @param type the instance type
     * @return the registered {@link InstanceSupplier} or {@code null}
     */
    <T> InstanceSupplier<T> getRegisteredInstanceSupplier(Class<T> type);

    void addCloseListener(ApplicationListener<BootstrapContextClosedEvent> listener);

    /**
     * The scope of an instance.
     *
     * @since 2.4.2
     */
    enum Scope {

        /**
         * A singleton instance. The {@link InstanceSupplier} will be called only once and
         * the same instance will be returned each time.
         */
        SINGLETON,

        /**
         * A prototype instance. The {@link InstanceSupplier} will be called whenever an
         * instance is needed.
         */
        PROTOTYPE

    }

    /**
     * Supplier used to provide the actual instance when needed.
     *
     * @param <T> the instance type
     * @see Scope
     */
    @FunctionalInterface
    interface InstanceSupplier<T> {

        /**
         * Factory method that can be used to create an {@link InstanceSupplier} for a
         * given instance.
         *
         * @param <T>      the instance type
         * @param instance the instance
         * @return a new {@link InstanceSupplier}
         */
        static <T> InstanceSupplier<T> of(T instance) {
            return (registry) -> instance;
        }

        /**
         * Factory method that can be used to create an {@link InstanceSupplier} from a
         * {@link Supplier}.
         *
         * @param <T>      the instance type
         * @param supplier the supplier that will provide the instance
         * @return a new {@link InstanceSupplier}
         */
        static <T> InstanceSupplier<T> from(Supplier<T> supplier) {
            return (registry) -> (supplier != null) ? supplier.get() : null;
        }

        /**
         * Factory method used to create the instance when needed.
         *
         * @param context the {@link BootstrapContext} which may be used to obtain other
         *                bootstrap instances.
         * @return the instance
         */
        T get(BootstrapContext context);

        /**
         * Return the scope of the supplied instance.
         *
         * @return the scope
         * @since 2.4.2
         */
        default Scope getScope() {
            return Scope.SINGLETON;
        }

        /**
         * Return a new {@link InstanceSupplier} with an updated {@link Scope}.
         *
         * @param scope the new scope
         * @return a new {@link InstanceSupplier} instance with the new scope
         * @since 2.4.2
         */
        default InstanceSupplier<T> withScope(Scope scope) {
            InstanceSupplier<T> parent = this;
            return new InstanceSupplier<T>() {

                @Override
                public T get(BootstrapContext context) {
                    return parent.get(context);
                }

                @Override
                public Scope getScope() {
                    return scope;
                }

            };
        }

    }

}
